library Spell requires /*
    */ Vector /*
    */ GroupUtils /*
    */ Practicals /*
    */ Dto /*
    */ Unit /*
    */

    globals
        private constant real INTERVAL = .03125
    endglobals

    private module DamageMeta
        attacktype m_attackType
        damagetype m_damageType
        weapontype m_weaponType

        string m_damageEffPath
        string m_attachPointName

        //伤害量
        real m_amount
        //伤害系数
        real m_amountRate
        //建筑伤害系数
        real m_structureAmountRate
        //是否攻击伤害
        boolean m_attack
        //是否远程攻击
        boolean m_ranged
        //不分敌我
        boolean m_ignoreAlliance

        method resetDamage takes nothing returns nothing
            set m_attackType = ATTACK_TYPE_CHAOS //混乱(不计算护甲) ATTACK_TYPE_NORMAL  //法术
            set m_damageType = DAMAGE_TYPE_NORMAL //普通(计算护甲) DAMAGE_TYPE_UNKNOWN //未知
            set m_weaponType = WEAPON_TYPE_WHOKNOWS //无
            set m_damageEffPath = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
            set m_attachPointName = "origin"
            set m_attack = true
            set m_ranged = false
            set m_ignoreAlliance = false
            set m_amount = 0
            set m_amountRate = 1.0
            set m_structureAmountRate = 1.0
        endmethod

        private method damageTypeFilter takes unit target returns boolean
            if m_damageType == DAMAGE_TYPE_COLD and GetUnitAbilityLevel(target, 'S000') > 0 then
                return false
            endif
            return true
        endmethod

        method damageUnit takes unit target returns nothing
            //伤害系数
            local real amount = this.m_amount * this.m_amountRate
            
            //死亡的
            if not IsWidgetAlive(target) then
                return
            endif
            //非敌人
            if not m_ignoreAlliance and not IsUnitEnemy(target, GetOwningPlayer(m_unit)) then
                return
            endif
            //伤害类型
            if not damageTypeFilter(target) then
                return
            endif

            //建筑目标
            if IsUnitType(target, UNIT_TYPE_STRUCTURE) then
                set amount = amount * this.m_structureAmountRate
            endif

            call UnitDamageTarget(m_unit, target, amount, m_attack, m_ranged, m_attackType, m_damageType, m_weaponType)
            if m_damageEffPath != null then
                call AddEffectTarget(m_damageEffPath, target, m_attachPointName)
            endif
        endmethod

        method damageRange takes real x, real y, real radius returns nothing
            local group g = NewGroup()
            local unit u

            call GroupUnitsInArea(g, x, y, radius)

            loop
                set u = FirstOfGroup(g)
                exitwhen u == null
                call GroupRemoveUnit(g, u)
                call this.damageUnit(u)
            endloop
            
            call ReleaseGroup(g)

            // TODO 伤害可破坏物

            set g = null
        endmethod

    endmodule


    struct Spell
        delegate Dto m_dto

        integer m_abilityId

        // 施法单位
        unit m_unit
        // 目标单位
        unit m_unitTarget
        // 是否在施法中
        boolean m_isSpelling

        real m_x
        real m_y
        real m_targetX
        real m_targetY

        //施法时间
        real m_timeCast
        //最大施法距离
        real m_distanceMax
        //最小施法距离
        real m_distanceMin
        //影响范围
        real m_rangeEffect
        //效果持续时间
        real m_timeDurative

        // 速度
        Vector m_velocity
        // 位置
        Vector m_position
        // 加速度(acceleration)
        Vector m_acc
        // 辅助向量
        Vector m_helpVector

        real m_angle
        real m_interval

        integer m_count

        // 重力加速度
        static Vector s_g

        private group m_grp
        private timer m_tm

        implement DamageMeta
        implement StructList

        method destroy takes nothing returns nothing
            if this.m_dto > 0 then
                call this.m_dto.destroy()
            endif
            if this.m_acc > 0 then
                call this.m_acc.destroy()
            endif
            if this.m_velocity > 0 then
                call this.m_velocity.destroy()
            endif
            if this.m_position > 0 then
                call this.m_position.destroy()
            endif
            if this.m_helpVector > 0 then
                call this.m_helpVector.destroy()
            endif

            if this.m_grp != null then
                call ReleaseGroup(this.m_grp)
            endif
            if this.m_tm != null then
                call ReleaseTimer(this.m_tm)
            endif

            set m_timeCast = 0.00
            set m_timeDurative = 0.00
            set m_distanceMax = 0.00
            set m_distanceMin = 0.00
            set m_rangeEffect = 0.00

            set m_velocity = 0
            set m_position = 0
            set m_acc = 0
            set m_helpVector = 0

            set this.m_grp = null
            set this.m_tm = null
            
            call this.listRemove()
            call this.deallocate()
        endmethod

        static method get takes unit u, integer abilityId returns thistype
            local thistype d = thistype.first
            loop
                exitwhen (d <= 0)
                if d.m_unit == u and d.m_abilityId == abilityId then
                    return d
                endif
                set d = d.next
            endloop
            return 0
        endmethod

        static method createNew takes nothing returns Spell
            local thistype this = Spell.allocate()
            set this.m_dto = Dto.create()
            set this.m_count = 0

            call this.resetDamage()
            call this.listAdd()

            return this
        endmethod    

        static method create takes unit u, integer abilityId returns Spell
            local thistype this = thistype.get(u, abilityId)
            if this > 0 then
                return this
            endif

            set this = createNew()
            set this.m_unit = u
            set this.m_abilityId = abilityId

            return this
        endmethod

        method getInnerTimer takes nothing returns timer
            if this.m_tm == null then
                set this.m_tm = NewTimer()
                call SetTimerData(this.m_tm, this)
            endif
            return this.m_tm
        endmethod

        method getInnerGroup takes nothing returns group
            if this.m_grp == null then
                set this.m_grp = NewGroup()
            endif
            return this.m_grp
        endmethod

        private static method onInit takes nothing returns nothing
            set s_g = Vector.create(0.0, 0.0, -800.0 * INTERVAL * INTERVAL)
        endmethod
    endstruct

endlibrary